這篇會把前一篇的單一main.lisp拆成多個檔案,並用ASDF的系統定義檔案管理載入順序與依賴。完成後,可以透過asdf:load-system載入abcl-javafx,執行結果仍然是相同的HelloFX視窗。
雖然可以繼續直接將所有程式碼寫入一份文件,但為了後續的管理,最好還是使用ASDF將程式碼拆成個別檔案。
ASDF的全名是「Another System Definition Facility」。是類似Maven(JAVA)、package.json(Node.JS)部分功能的構建工具,申明系統依賴、套件依賴、檔案依賴。
建立一個與系統同名的.asd檔案。以目前例子用abcl-javafx作爲系統名稱,建立abcl-javafx.asd檔案:
(defsystem "abcl-javafx"
:description "Create GUI Application with ABCL and JavaFX"
:version "0.0.1"
:author "lagagain <lagagain@email.com>"
:licence "MIT"
:serial t ;; 申明需要依順序載入
:components ((:file "load-lib")
(:file "package")
(:file "constants")
(:file "main")))
除了最基本的系統名稱、描述、作者、版本外,最重要的是定義檔案載入的順序。雖然同樣可以在檔案中使用(load <file-path>),或是利用#'require、#'provide來定義檔案依賴,但使用ASDF是三者最簡單、容易管理的方式。在上面例子中使用:serial t申明必須依序載入,並且組成的檔案依序有:
load-lib.lisp
package.lisp
constants.lisp
main.lisp
到目前為止,這些檔案的副檔名都是.lisp,在ASDF申明中可以省略不寫。load-lib內容放在最後說明,所以首先是定義package:
(defpackage :abcl-javafx
(:use :cl :java))
在之後的檔案內容都會有(in-package :abcl-javafx),申明使用的Package空間。現在除了基本的:cl(common-lisp),主要先依賴載入:java的符號(symbol)。然後是定義常數constants:
(in-package :abcl-javafx)
(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))
constants內容定義JAVA Class的別名,這裡不用更常使用的defconstant,是因為有些JavaFX類別需要延後載入,或是可以用JAVA內部類別名稱(簽名),其實也就是將#'jclass函式拿掉,不直接取得JAVA類別物件,直接定義成字串:
(in-package :abcl-javafx)
(defconstant +application+ "javafx.application.Application")
(defconstant +scene+ "javafx.scene.Scene")
(defconstant +label+ "javafx.scene.control.Label")
(defconstant +StackPane+ "javafx.scene.layout.StackPane")
(defconstant +stage+ "javafx.stage.Stage")
最後是主程式main:
(in-package :abcl-javafx)
(let* ((HelloFX (jnew-runtime-class "HelloFX"
:superclass "javafx.application.Application"
:methods `(("main" :void ()
(lambda (this)
(jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
("start" :void ("javafx.stage.Stage")
(lambda (this stage)
(prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
(javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
(l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
(scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
(jcall "setScene" stage scene)
(jcall "show" stage)))))
:access-flags '(:public))))
(jcall "main" (jnew HelloFX)))
可以在把主類別的方法提取出來,會更清楚段落內容:
(in-package :abcl-javafx)
(defun HelloFX.main (this)
(jcall "launch" this (jnew-array "java.lang.String" 1)))
;; 定義HelloFX.main函式
(defun HelloFX.main (this)
(jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1)))
;; 定義HelloFX.start函式
(defun HelloFX.start (this stage)
(prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
(javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
(l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
(scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
(jcall "setScene" stage scene)
(jcall "show" stage)))
;; 簡化主程式內容
(let* ((HelloFX (jnew-runtime-class "HelloFX"
:superclass +application+
:methods '(("main" :void ()
HelloFX.main)
("start" :void ("javafx.stage.Stage")
HelloFX.start))
:access-flags '(:public))))
(jcall "main" (jnew HelloFX)))
最後是載入lib資料夾下的JAR檔案的部分load-lib:
(ignore-errors
(let (libs)
(setf libs
(loop for file in (directory "lib/*.jar")
do
(setf file (namestring file))
(print file)
collect file))
(add-to-classpath libs)))
不過其實ABCL有abcl-asdf擴充ASDF可用的定義內容,可以使用其中的jar-directory來取代load-lib,以下是調整後的abcl-javafx.asd內容:
(defsystem "abcl-javafx"
:description "Create GUI Application with ABCL and JavaFX"
:version "0.0.1"
:author "lagagain <lagagain@email.com>"
:licence "MIT"
:defsystem-depends-on (abcl-asdf) ;; 引入`abcl-asdf`
:depends-on ()
:serial t ;; 申明需要依順序載入
:components ((:jar-directory "lib") ;; 直接使用`jar-directory`載入lib資料夾下的JAR檔案
(:file "package")
(:file "constants")
(:file "main")))
或者可以在運行JAVA的時候,直接申明搜尋的CLASSPATH或Module路徑:
alias abcl='java --enable-native-access=javafx.graphics --module-path "${SDK_PATH}" --add-modules=javafx.controls,javafx.fxml,javafx.graphics --add-opens java.base/java.lang=ALL-UNNAMED -jar "${ABCL_PATH}"'
進到REPL模式後輸入:
(require :abcl-contrib) ;; 如果沒有使用到`abcl-asdf`的話可以省略,只載入`(require :asdf)`
(push #P"./" asdf:*central-registry*) ;; 將當前目錄加入system搜尋路徑
(asdf:load-system "abcl-javafx")
同樣可以將上述內容儲存成run-system.lisp,然後透過--load載入:
abcl --load run-system.lisp --eval '(quit)'
將上述檔案依照.asd中的順序放在同一個目錄後,執行這個指令即可載入系統。成功時會看到與前一篇相同的HelloFX視窗;關閉視窗後,程式會回到終端機。這也確認ASDF已經依序載入各個檔案,並完成JavaFX應用程式的建立。

下一篇將在這個ASDF系統的基礎上,介紹ABCL與Java的交互方式、JSS Package,以及如何使用JavaFX建立場景。